/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.workflow.simple.alfresco.conversion;

import java.util.Arrays;

import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
import org.activiti.bpmn.model.ServiceTask;
import org.activiti.bpmn.model.UserTask;
import org.activiti.workflow.simple.alfresco.conversion.script.PropertyReference;
import org.activiti.workflow.simple.alfresco.conversion.script.ScriptServiceTaskBuilder;
import org.activiti.workflow.simple.alfresco.conversion.script.ScriptTaskListenerBuilder;
import org.activiti.workflow.simple.alfresco.form.AlfrescoTransitionsPropertyDefinition;
import org.activiti.workflow.simple.alfresco.model.M2AssociationSource;
import org.activiti.workflow.simple.alfresco.model.M2AssociationTarget;
import org.activiti.workflow.simple.alfresco.model.M2ClassAssociation;
import org.activiti.workflow.simple.alfresco.model.M2Model;
import org.activiti.workflow.simple.alfresco.model.M2Type;
import org.activiti.workflow.simple.alfresco.step.AlfrescoEndProcessStepDefinition;
import org.activiti.workflow.simple.alfresco.step.AlfrescoReviewStepDefinition;
import org.activiti.workflow.simple.converter.ConversionConstants;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.converter.step.BaseStepDefinitionConverter;
import org.activiti.workflow.simple.definition.ChoiceStepsDefinition;
import org.activiti.workflow.simple.definition.ConditionDefinition;
import org.activiti.workflow.simple.definition.HumanStepAssignment.HumanStepAssignmentType;
import org.activiti.workflow.simple.definition.HumanStepDefinition;
import org.activiti.workflow.simple.definition.ListConditionStepDefinition;
import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.definition.form.FormDefinition;
import org.activiti.workflow.simple.definition.form.ListPropertyEntry;

public class AlfrescoReviewStepConverter extends BaseStepDefinitionConverter<AlfrescoReviewStepDefinition, FlowElement> {

    private static final long serialVersionUID = 1L;

    @Override
    public Class<? extends StepDefinition> getHandledClass() {
        return AlfrescoReviewStepDefinition.class;
    }

    @Override
    protected FlowElement createProcessArtifact(AlfrescoReviewStepDefinition stepDefinition,
                                                WorkflowDefinitionConversion conversion) {
        FlowElement lastElement;

        M2Model model = AlfrescoConversionUtil.getContentModel(conversion);
        String namespacePrefix = model.getNamespaces().get(0).getPrefix();

        String id = stepDefinition.getId();
        if (id == null) {
            id = AlfrescoConversionUtil.getValidIdString(stepDefinition.getName());
        }

        // Break down the review into separate steps and convert those instead
        HumanStepDefinition reviewTask = new HumanStepDefinition();
        reviewTask.setName(stepDefinition.getName());
        reviewTask.setDescription("Review task");

        // Clone the review-form and add custom transitions property
        FormDefinition finalForm = null;
        if (stepDefinition.getForm() != null) {
            finalForm = stepDefinition.getForm().clone();
        } else {
            finalForm = new FormDefinition();
        }
        finalForm.addFormProperty(createTransitionsProperty());
        reviewTask.setForm(finalForm);


        // Assignment
        if (stepDefinition.getAssignmentType() == HumanStepAssignmentType.USER) {
            reviewTask.setAssignee(new PropertyReference(stepDefinition.getAssignmentPropertyName()).getPlaceholder());
        }

        // Add a script-task that initializes the correct variables for the review
        ScriptServiceTaskBuilder builder = new ScriptServiceTaskBuilder();
        builder.setExecutionVariable(getCountVariableName(id, namespacePrefix), "0");

        String requiredCount = null;
        if (stepDefinition.getRequiredApprovalCount() != null) {
            if (PropertyReference.isPropertyReference(stepDefinition.getRequiredApprovalCount())) {
                PropertyReference reference = PropertyReference.createReference(stepDefinition.getRequiredApprovalCount());
                requiredCount = reference.getVariableReference(namespacePrefix);
                AlfrescoConversionUtil.getPropertyReferences(conversion).add(reference);
            } else {
                // No reference, use explicit value
                requiredCount = stepDefinition.getRequiredApprovalCount();
            }
        } else {
            requiredCount = "1";
        }
        builder.setExecutionVariable(getRequiredCountVariableName(id, namespacePrefix), requiredCount);

        ServiceTask serviceTask = builder.build();
        serviceTask.setName("Review initialization");
        serviceTask.setId(conversion.getUniqueNumberedId(ConversionConstants.SERVICE_TASK_ID_PREFIX));

        addFlowElement(conversion, serviceTask, true);

        // Create the actual element
        UserTask userTask = (UserTask) conversion.getConversionFactory().getStepConverterFor(reviewTask)
                .convertStepDefinition(reviewTask, conversion);
        lastElement = userTask;


        M2Type userTaskType = model.getType(userTask.getFormKey());

        // Update parent, since we use an "outcome" for this task
        userTaskType.setParentName(AlfrescoConversionConstants.OUTCOME_BASE_FORM_TYPE);

        // Add script to the complete-task listener to update approval count (if needed)
        ScriptTaskListenerBuilder listenerBuilder = AlfrescoConversionUtil.getScriptTaskListenerBuilder(conversion, userTask.getId(), AlfrescoConversionConstants.TASK_LISTENER_EVENT_COMPLETE);
        String approverCount = getCountVariableName(id, namespacePrefix);
        listenerBuilder.addLine("if(task.getVariableLocal('" + getTransitionProperty(userTaskType, namespacePrefix) + "') == '" + AlfrescoConversionConstants.TRANSITION_APPROVE + "') {");
        listenerBuilder.addLine("execution.setVariable('" + approverCount + "', " + approverCount + " + 1);");
        listenerBuilder.addLine("}");

        if (stepDefinition.getAssignmentType() == HumanStepAssignmentType.USERS) {
            String assignmentVariableName = id + "Assignee";

            // Add the assignee-property to the content-model
            M2ClassAssociation reviewAssignee = new M2ClassAssociation();
            M2AssociationTarget target = new M2AssociationTarget();
            target.setClassName(AlfrescoConversionConstants.CONTENT_TYPE_PEOPLE);
            target.setMandatory(true);
            target.setMany(false);

            M2AssociationSource source = new M2AssociationSource();
            source.setMany(false);
            source.setMandatory(true);

            reviewAssignee.setName(AlfrescoConversionUtil.getQualifiedName(namespacePrefix, assignmentVariableName));
            reviewAssignee.setTarget(target);
            reviewAssignee.setSource(source);

            userTaskType.getAssociations().add(reviewAssignee);
            userTask.setAssignee(new PropertyReference(assignmentVariableName).getUsernameReferenceExpression(namespacePrefix));

            // Finally, add the multi-instance characteristics to the userTask
            MultiInstanceLoopCharacteristics mi = new MultiInstanceLoopCharacteristics();
            mi.setCompletionCondition(getCompletionCondition(id, namespacePrefix));
            mi.setElementVariable(new PropertyReference(assignmentVariableName).getVariableReference(namespacePrefix));

            PropertyReference reference = null;
            if (PropertyReference.isPropertyReference(stepDefinition.getAssignmentPropertyName())) {
                reference = PropertyReference.createReference(stepDefinition.getAssignmentPropertyName());
            } else {
                reference = new PropertyReference(stepDefinition.getAssignmentPropertyName());
            }
            mi.setInputDataItem(reference.getVariableReference(namespacePrefix));
            AlfrescoConversionUtil.getPropertyReferences(conversion).add(reference);
            mi.setSequential(false);
            userTask.setLoopCharacteristics(mi);
        }

        if (stepDefinition.getRejectionSteps() != null) {
            // Create choice-step
            ChoiceStepsDefinition choice = new ChoiceStepsDefinition();
            choice.setId(id + "choice");

            // Add rejection steps to the choice
            ListConditionStepDefinition<ChoiceStepsDefinition> rejectStepList = new ListConditionStepDefinition<ChoiceStepsDefinition>();
            rejectStepList.setName("Rejected");
            for (StepDefinition child : stepDefinition.getRejectionSteps()) {
                rejectStepList.addStep(child);
            }

            // Add end-process step to reject path, if needed
            if (stepDefinition.isEndProcessOnReject()) {
                rejectStepList.addStep(new AlfrescoEndProcessStepDefinition());
            }

            // Make choice condition based on review outcome
            ConditionDefinition condition = new ConditionDefinition();
            condition.setLeftOperand(getCountVariableName(id, namespacePrefix));
            condition.setOperator("<");
            condition.setRightOperand(getRequiredCountVariableName(id, namespacePrefix));
            rejectStepList.setConditions(Arrays.asList(condition));
            choice.addStepList(rejectStepList);

            // Add default (empty) choice for approval AFTER the review-one
            ListConditionStepDefinition<ChoiceStepsDefinition> defaultStepList = new ListConditionStepDefinition<ChoiceStepsDefinition>();
            defaultStepList.setName("Approved");
            choice.addStepList(defaultStepList);

            // Convert the choice-step
            lastElement = (FlowElement) conversion.getConversionFactory().getStepConverterFor(choice)
                    .convertStepDefinition(choice, conversion);
        }
        return lastElement;
    }

    protected String getTransitionProperty(M2Type type, String namespacePrefix) {
        return new PropertyReference(AlfrescoConversionUtil.getValidIdString(type.getName() + AlfrescoConversionConstants.PROPERTY_TRANSITIONS_SUFFIX)).getVariableReference(namespacePrefix);
    }

    protected String getCompletionCondition(String id, String namespacePrefix) {
        return "${" + getCountVariableName(id, namespacePrefix) + " >= " + getRequiredCountVariableName(id, namespacePrefix) + "}";
    }

    protected String getCountVariableName(String id, String namespacePrefix) {
        return new PropertyReference(id + "ApprovalCount").getVariableReference(namespacePrefix);
    }

    protected String getRequiredCountVariableName(String id, String namespacePrefix) {
        return new PropertyReference(id + "RequiredApprovalCount").getVariableReference(namespacePrefix);
    }

    protected AlfrescoTransitionsPropertyDefinition createTransitionsProperty() {
        AlfrescoTransitionsPropertyDefinition prop = new AlfrescoTransitionsPropertyDefinition();
        prop.addEntry(new ListPropertyEntry(AlfrescoConversionConstants.TRANSITION_APPROVE,
                AlfrescoConversionConstants.TRANSITION_APPROVE));
        prop.addEntry(new ListPropertyEntry(AlfrescoConversionConstants.TRANSITION_REJECT,
                AlfrescoConversionConstants.TRANSITION_REJECT));
        return prop;
    }

}
